/*
 * Copyright 2012-2019 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.springframework.boot.context.annotation;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.ImportSelector;
import org.springframework.core.OrderComparator;
import org.springframework.core.Ordered;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;

/**
 * 一组可在 {@link ApplicationContext} 中注册的 {@link Configuration @Configuration} 类。
 * 这些类可以通过使用 {@link #getClasses(Configurations[])} 从一个或多个
 * {@link Configurations} 实例中返回。返回的类数组遵循通常由
 * {@link ApplicationContext} 和/或自定义 {@link ImportSelector} 实现所应用的排序规则。
 * <p>
 * 此类主要用于需要指定配置类但无法使用 {@link SpringRunner} 的测试场景。
 * <p>
 * 该类的实现应使用 {@code @Order} 注解或实现 {@link Ordered} 接口以指定顺序。
 *
 * @author Phillip Webb
 * @since 2.0.0
 * @see UserConfigurations
 */
public abstract class Configurations {

	private static final Comparator<Object> COMPARATOR = OrderComparator.INSTANCE
			.thenComparing((other) -> other.getClass().getName());

	private final Set<Class<?>> classes;

	protected Configurations(Collection<Class<?>> classes) {
		Assert.notNull(classes, "Classes must not be null");
		Collection<Class<?>> sorted = sort(classes);
		this.classes = Collections.unmodifiableSet(new LinkedHashSet<>(sorted));
	}

	/**
	 * 对配置类进行排序，按照它们应该被应用的顺序。
	 * @param classes 需要排序的类集合
	 * @return 已排序的类集合
	 */
	protected Collection<Class<?>> sort(Collection<Class<?>> classes) {
		return classes;
	}

	protected final Set<Class<?>> getClasses() {
		return this.classes;
	}

	/**
	 * 合并来自同一类型的另一个配置源的配置。
	 * @param other 另一个 {@link Configurations} 实例（必须与当前实例类型相同）
	 * @return 一个新的配置实例（必须与当前实例类型相同）
	 */
	protected Configurations merge(Configurations other) {
		Set<Class<?>> mergedClasses = new LinkedHashSet<>(getClasses());
		mergedClasses.addAll(other.getClasses());
		return merge(mergedClasses);
	}

	/**
	 * 合并配置。
	 * @param mergedClasses 合并后的类集合
	 * @return 一个新的配置实例（必须与当前实例类型相同）
	 */
	protected abstract Configurations merge(Set<Class<?>> mergedClasses);

	/**
	 * 返回所有指定配置中的类，按照它们被注册的顺序。
	 * @param configurations 源配置数组
	 * @return 按注册顺序排列的配置类数组
	 */
	public static Class<?>[] getClasses(Configurations... configurations) {
		return getClasses(Arrays.asList(configurations));
	}

	/**
	 * 返回所有指定配置中的类，按照它们被注册的顺序。
	 * @param configurations 源配置集合
	 * @return 按注册顺序排列的配置类数组
	 */
	public static Class<?>[] getClasses(Collection<Configurations> configurations) {
		List<Configurations> ordered = new ArrayList<>(configurations);
		ordered.sort(COMPARATOR);
		List<Configurations> collated = collate(ordered);
		LinkedHashSet<Class<?>> classes = collated.stream().flatMap(Configurations::streamClasses)
				.collect(Collectors.toCollection(LinkedHashSet::new));
		return ClassUtils.toClassArray(classes);
	}

	private static Stream<Class<?>> streamClasses(Configurations configurations) {
		return configurations.getClasses().stream();
	}

	private static List<Configurations> collate(List<Configurations> orderedConfigurations) {
		LinkedList<Configurations> collated = new LinkedList<>();
		for (Configurations item : orderedConfigurations) {
			if (collated.isEmpty() || collated.getLast().getClass() != item.getClass()) {
				collated.add(item);
			}
			else {
				collated.set(collated.size() - 1, collated.getLast().merge(item));
			}
		}
		return collated;
	}

}
